﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Soap;
using System.Threading;
using System.Windows.Forms;
using Kyoh.Lib.WinForms;
using Kyoh.Lib.Win32API;

namespace Kyoh.Lib.WinForms
{
    /// <summary>二重起動を禁止する <see cref="Form"/> のラッパークラスを提供します。</summary>
	public partial class MutexForm : Form
	{
		private int WM_APP_CLOSENEWFORM;
		private int WM_APP_FINDEXISTINGFORMS;

		private Mutex _mutex;

		private SoapFormatter format = new SoapFormatter();

		#region イベント

        /// <summary>後から起動された二重起動のフォームが閉じる前に呼び出されるイベントの引数を定義します。</summary>
		public class AnotherFormClosingEventArgs : CancelEventArgs
		{
            /// <summary><see cref="AnotherFormClosingEventArgs"/> クラスの新しいインスタンスを初期化します。</summary>
            public AnotherFormClosingEventArgs() : base() { }
            /// <summary><see cref="AnotherFormClosingEventArgs"/> クラスの新しいインスタンスを初期化します。</summary>
            /// <param name="cancel"><see cref="AnotherFormClosingEventArgs"/> クラスの新しいインスタンスにおいて、キャンセルするかどうかの初期値。</param>
            public AnotherFormClosingEventArgs(bool cancel) : base(cancel) { }
		}

        /// <summary>後から起動された二重起動のフォームが閉じた後に呼び出されるイベントの引数を定義します。</summary>
        public class AnotherFormClosedEventArgs : EventArgs
		{
            /// <summary>後から起動された二重起動のフォームに対して渡されたコマンドライン引数を取得します。</summary>
			public string[] CommandLine { get; internal set; }
            /// <summary><see cref="AnotherFormClosedEventArgs"/> クラスの新しいインスタンスを初期化します。</summary>
			public AnotherFormClosedEventArgs() : this(new string[0]) { }
            /// <summary><see cref="AnotherFormClosedEventArgs"/> クラスの新しいインスタンスを初期化します。</summary>
            /// <param name="args">後から起動された二重起動のフォームに対して渡されたコマンドライン引数。</param>
            public AnotherFormClosedEventArgs(string[] args) { CommandLine = args; }
		}

		private object AnotherFormClosingKey = new object();
        /// <summary>二重起動時、既存のプロセスからフォームを閉じる要求を受けると発生するイベントのハンドラを定義します。</summary>
		public delegate void AnotherFormClosingHandler(object sender, AnotherFormClosingEventArgs e);
		/// <summary>二重起動時、既存のプロセスからフォームを閉じる要求を受けると発生するイベントを追加・削除します。</summary>
        protected event AnotherFormClosingHandler AnotherFormClosing
		{
			add { Events.AddHandler(AnotherFormClosingKey, value); }
			remove { Events.RemoveHandler(AnotherFormClosingKey, value); }
		}
        /// <summary>二重起動時、既存のプロセスからフォームを閉じる要求を受けた際の挙動を定義します。</summary>
        /// <param name="e"><see cref="AnotherFormClosing"/> イベントの引数。</param>
        protected virtual void OnAnotherFormClosing(AnotherFormClosingEventArgs e) { }
        private void RaiseAnotherFormClosing(AnotherFormClosingEventArgs e)
        {
            this.RaiseEvent(AnotherFormClosingKey, e);
            this.OnAnotherFormClosing(e);
        }

		private object AnotherFormClosedKey = new object();
        /// <summary>二重起動のプロセスのメインフォームが現在のインスタンスからの要求に従って閉じた後に発生するイベントのハンドラを定義します。</summary>
        public delegate void AnotherFormClosedHandler(object sender, AnotherFormClosedEventArgs e);
		/// <summary>二重起動のプロセスのメインフォームが現在のインスタンスからの要求に従って閉じた後に発生するイベントを追加・削除します。</summary>
		protected event AnotherFormClosedHandler AnotherFormClosed
		{
			add { Events.AddHandler(AnotherFormClosedKey, value); }
			remove { Events.RemoveHandler(AnotherFormClosedKey, value); }
		}
        /// <summary>二重起動のプロセスのメインフォームが現在のインスタンスからの要求に従って閉じた際の挙動を定義します。</summary>
        /// <param name="e"><see cref="AnotherFormClosed"/> イベントの引数。</param>
        protected virtual void OnAnotherFormClosed(AnotherFormClosedEventArgs e) { }
        private void RaiseAnotherFormClosed(AnotherFormClosedEventArgs e)
        {
            this.RaiseEvent(AnotherFormClosedKey, e);
            this.OnAnotherFormClosed(e);
        }

		private void RaiseEvent(object key, EventArgs e)
		{
			Delegate temp = Events[key];
			if (temp != null)
				temp.DynamicInvoke(this, e);
		}

		#endregion

        /// <summary><see cref="MutexForm"/> クラスの新しいインスタンスを初期化します。</summary>
        protected MutexForm()
		{
			InitializeComponent();

			WM_APP_CLOSENEWFORM = NativeMethods.RegisterWindowMessage(GetType().FullName + "::CloseNewForm");
			WM_APP_FINDEXISTINGFORMS = NativeMethods.RegisterWindowMessage(GetType().FullName + "::FindExistingForms");

			// 二重起動のチェックと二重起動時のメッセージ発行
			_mutex = new Mutex(false, GetType().FullName);
			if (!_mutex.WaitOne(0, false))
				NativeMethods.PostMessage(HWND_BROADCAST, WM_APP_FINDEXISTINGFORMS, this.Handle, this.Handle);
		}

		private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);

        /// <summary>既定の WndProc 定義を更新します。</summary>
        /// <param name="m">ウィンドウ・メッセージ</param>
        [DebuggerStepThrough]
		protected override void WndProc(ref Message m)
		{
			// 二重起動したプロセスから、既存プロセス探索のメッセージを受けた
			//  → 自分が既存のプロセスであることを表明し、クローズ要求を発行する。また、このフォームをアクティブにする。
			if (m.Msg == WM_APP_FINDEXISTINGFORMS)
			{
				if (m.WParam != IntPtr.Zero && m.WParam != this.Handle && m.WParam != HWND_BROADCAST && m.LParam == m.WParam)
					if (NativeMethods.SendMessage(m.WParam, WM_APP_CLOSENEWFORM, this.Handle, this.Handle) != IntPtr.Zero)
						Activate();
			}
			// 既存プロセスから、フォームのクローズ要求を受けた
			//  → コマンドライン引数をSoap文字列で引き渡し、フォームを閉じる。
			else if (m.Msg == WM_APP_CLOSENEWFORM)
			{
				if (m.WParam != IntPtr.Zero && m.WParam != this.Handle && m.WParam != HWND_BROADCAST && m.LParam == m.WParam)
				{
					AnotherFormClosingEventArgs e = new AnotherFormClosingEventArgs();
                    this.RaiseAnotherFormClosing(e);

					// イベントのキャンセル要求により中止する。
					if (e.Cancel)
						NativeMethods.ReplyMessage(IntPtr.Zero);
					else
					{
						string[] args = Environment.GetCommandLineArgs();
						using (MemoryStream stream = new MemoryStream())
						{
							format.Serialize(stream, args);

							byte[] buffer = stream.GetBuffer();
							IntPtr pBuffer = Marshal.AllocHGlobal(buffer.Length);
							Marshal.Copy(buffer, 0, pBuffer, buffer.Length);
							CopyData copy = new CopyData() { cbData = (uint)buffer.Length, lpData = pBuffer };
							NativeMethods.SendMessage(m.WParam, Constants.WM_COPYDATA, this.Handle, ref copy);
							Marshal.FreeHGlobal(pBuffer);
						}
						Close();
						NativeMethods.ReplyMessage(new IntPtr(1));
					}
				}
			}

			// 二重起動したプロセスから、コマンドライン引数の引き渡しが行われた
			//  → コマンドラインを受け取り、イベントを発生させる。
			else if (m.Msg == Constants.WM_COPYDATA)
			{
				CopyData copy = (CopyData)Marshal.PtrToStructure(m.LParam, typeof(CopyData));
				byte[] buffer = new byte[copy.cbData];
				Marshal.Copy(copy.lpData, buffer, 0, buffer.Length);

				NativeMethods.ReplyMessage(IntPtr.Zero);

				string[] args;
				using (MemoryStream stream = new MemoryStream(buffer, false))
					args = (string[])format.Deserialize(stream);
                this.RaiseAnotherFormClosed(new AnotherFormClosedEventArgs(args));
			}
			base.WndProc(ref m);
		}
	}
}
